%!Tex Program = xelatex
%\documentclass[a4paper]{article}
\documentclass[a4paper]{ctexart}
\usepackage{xltxtra}
\usepackage{listings}
%\setmainfont[Mapping=tex-text]{AR PL UMing CN:style=Light}
%\setmainfont[Mapping=tex-text]{AR PL UKai CN:style=Book}
%\setmainfont[Mapping=tex-text]{WenQuanYi Zen Hei:style=Regular}
%\setmainfont[Mapping=tex-text]{WenQuanYi Zen Hei Sharp:style=Regular}
%\setmainfont[Mapping=tex-text]{AR PL KaitiM GB:style=Regular} 
%\setmainfont[Mapping=tex-text]{AR PL SungtiL GB:style=Regular} 
%\setmainfont[Mapping=tex-text]{WenQuanYi Zen Hei Mono:style=Regular} 


\title{shell}
\author{zza}
\date{}
\begin{document}
\maketitle
\pagestyle{empty}

您可能想知道set-group-id和set-user-id(也称为

Set-gid和set-uid)位。set-uid位给予程序的权限

它的所有者，而不是它的用户，而set-gid位给程序权限

在它的基团中。这些位是用chmod设置的，使用s和g选项。的set-gid

set-uid标志对包含shell脚本的文件没有影响，只对可执行文件有影响

二进制文件

我们有点超前了，但下面是一个如何测试状态的例子

文件/bin/bash，这样您就可以看到它们在使用时的样子

\begin{lstlisting}[language=sh]
#!/bin/sh
if [ -f /bin/bash ]
then
echo “file /bin/bash exists”
fi
if [ -d /bin/bash ]
then
echo “/bin/bash is a directory”
else
echo “/bin/bash is NOT a directory”
fi
\end{lstlisting}
在测试为真之前，所有文件条件测试都要求该文件也存在。这个列表包含

只是测试命令中更常用的选项，所以要获得完整的列表，请参阅手册

条目。如果您使用的是bash(其中内置了测试)，请使用help test命令获取更多详细信息。

我们将在本章后面使用其中一些选项。

现在您已经了解了条件，您可以查看使用条件的控制结构。
控制结构

shell有一组控制结构，这与其他编程语言非常相似。

如果

if语句非常简单:它测试命令的结果，然后有条件地执行命令

声明组:

如果条件

然后

语句

在接下来的部分中，语句是要执行的一系列命令

当、同时或直到条件满足为止
其他的

语句

fi

if的一个常见用法是问一个问题，然后根据答案做决定


\begin{lstlisting}[language=sh]
#!/bin/sh
echo “Is it morning? Please answer yes or no”
read timeofday
if [ $timeofday = “yes” ]; then
echo “Good morning”
else
echo “Good afternoon”
fi
exit 0

\end{lstlisting}
这将给出以下输出:

现在是早上吗?请回答是或不是

是的

早上好

美元

该脚本使用[命令测试变量timeofday的内容。计算结果为

if命令，然后允许执行不同的代码行。

elif

不幸的是，这个非常简单的脚本存在几个问题。首先，它会采取任何措施

回答，除了“是”，意思是“不是”。您可以通过使用elif结构来防止这种情况，它允许您

添加第二个条件，以便在执行if的else部分时进行检查。

试着用elif做检查

您可以修改前面的脚本，以便在用户输入其他内容时报告错误消息

然后是“是”或“不是”。通过将else替换为elif，然后添加另一个条件来实现:
\begin{lstlisting}[language=sh]
#!/bin/sh
echo “Is it morning? Please answer yes or no”
read timeofday
if [ $timeofday = “yes” ]
then
echo “Good morning”
elif [ $timeofday = “no” ]; then
echo “Good afternoon”
else
echo “Sorry, $timeofday not recognized. Enter yes or no”
exit 1
fi
exit 0
\end{lstlisting}
它是如何工作的

这与前面的示例非常相似，但是现在elif命令再次测试变量if

第一个if条件不为真。如果两个测试都不成功，则打印错误消息，并使用

脚本以值1退出，调用者可以在调用程序中使用该值来检查脚本是否存在

是成功的。

变量的问题

这修复了最明显的缺陷，但潜伏着一个更微妙的问题。试试这个新脚本，但是只要按一下

输入(或某些键盘上的返回)，而不是回答问题。你会得到这样的错误信息:

[: =:一元运算符

出了什么问题?问题出在第一个if子句中。当测试变量timeofday时，它

由一个空字符串组成。因此，if子句看起来像
\begin{lstlisting}[language=sh]
if [ = “yes” ]
\end{lstlisting}
这不是一个有效条件。为了避免这种情况，你必须在变量周围使用引号:
\begin{lstlisting}[language=sh]
if [ “$timeofday” = “yes” ]
\end{lstlisting}
然后一个空变量给出有效的测试:
\begin{lstlisting}[language=sh]
if [ “” = “yes” ]
\end{lstlisting}
新的脚本如下:
\begin{lstlisting}[language=sh]
#!/bin/sh
echo “Is it morning? Please answer yes or no”
read timeofday
if [ “$timeofday” = “yes” ]
then
echo “Good morning”
elif [ “$timeofday” = “no” ]; then
echo “Good afternoon”
else
echo “Sorry, $timeofday not recognized. Enter yes or no”
exit 1
fi
exit 0
\end{lstlisting}
如果用户在回答问题时按Enter键，这是安全的。

“现在是早上吗?”请回答是或不是。”

注意，您需要在结束引号之前留下一个额外的空格，以便在用户输入的响应之前有一个空白，这样看起来更整洁。

为

使用for构造循环遍历一个值范围，该值范围可以是任何字符串集。它们可能是

简单地在程序中列出，或者更常见的是文件名的shell展开的结果。

语法很简单:

对于值中的变量

做

语句

完成

尝试使用固定字符串的for循环

这些值通常是字符串，所以你可以这样写:
\begin{lstlisting}[language=sh]
#!/bin/sh
for foo in bar fud 43
do
echo $foo
done
exit 0
\end{lstlisting}
输出结果如下:
bar
fud
43
如果你把bar fud 43中的第一行改成for foo，会发生什么呢

在“bar fud 43”里找食物?请记住，添加引号告诉shell

将它们之间的所有内容视为单个字符串。这是一种得到

存储在变量中的空格。
它是如何工作的

本例创建变量foo，并在每次for循环时为其分配不同的值。

由于shell默认认为所有变量都包含字符串，因此使用字符串43与使用字符串43一样有效

绳子断了。

尝试使用带有通配符展开的for循环

如前所述，通常将for循环与文件名的shell展开一起使用。这意味着

对字符串值使用通配符，并让shell在运行时填充所有值。

首先，您已经在原始示例中看到了这一点。脚本使用shell展开，*展开为当前目录下所有文件的名称。这些依次被用作变量

\verb|$file|在for循环中。

让我们快速查看另一个通配符扩展。假设您想打印当前目录中以字母“f”开头的所有脚本文件，并且您知道所有脚本都以.sh结尾。你可以

这样做:

\begin{lstlisting}[language=sh]

#!/bin/sh
for file in $(ls f*.sh); do
lpr $file
done
exit 0
\end{lstlisting}

它是如何工作的

这说明了\verb|$(command)|语法的使用，稍后将详细介绍(在关于

命令执行)。命令的输出提供了for命令的参数列表

命令包含在\verb|$()|序列中。

shell展开f*.sh，给出与此模式匹配的所有文件的名称。

请记住，shell脚本中所有变量的展开都是在脚本运行时完成的

执行，而不是在编写时执行，因此会发现变量声明中的语法错误

只有在执行时，如前面引用空变量时所示。
而

因为默认情况下所有shell值都被认为是字符串，所以for循环很适合遍历a

字符串的序列，但是当你不知道你需要多少次

要执行的循环。

当您需要重复一系列命令，但事先不知道它们重复了多少次时

应该执行，你通常会使用一个while循环，它有以下语法:

当条件发生时

语句

完成

例如，下面是一个相当糟糕的密码检查程序:
\begin{lstlisting}[language=sh]
#!/bin/sh
echo “Enter password”
read trythis
while [ “$trythis” != “secret” ]; do
echo “Sorry, try again”
read trythis
done
exit 0
\end{lstlisting}
该脚本的输出示例如下:
Enter password
password
Sorry, try again
secret
\textdollar
显然，这不是一种非常安全的要求密码的方式，但它确实有助于说明这一点

声明。do和done之间的语句将连续执行，直到条件为no为止

再真实的。在本例中，您要检查trythis的值是否等于secret。循环将会

继续，直到\verb|$trythis|等于secret。然后在语句imme处继续执行该脚本，紧跟在done之后。
直到

until语句的语法如下:
until condition
do
statements
done
这与while循环非常相似，但是条件测试反过来了。换句话说，循环循环一直持续到条件为真，而不是在条件为真时。
一般来说，如果一个循环应该至少执行一次，使用while循环;如果可以的话
根本不需要执行，使用一个until循环。
作为until循环的一个示例，您可以设置一个警报，当另一个用户的
您在命令行传递的登录名，登录:
\begin{lstlisting}[language=sh]
#!/bin/bash
until who | grep “$1” > /dev/null
do
sleep 60
done
# now ring the bell and announce the expected user.
echo -e ‘\a’ 
echo “**** $1 has just logged in ****“
exit 0
\end{lstlisting}
如果用户已经登录，则根本不需要执行循环，因此使用until更为自然

选择比而。

情况下

case结构比您目前遇到的情况稍微复杂一些。它的语法如下
\begin{lstlisting}[language=sh]
case variable in
pattern [ | pattern] ...) statements;;
pattern [ | pattern] ...) statements;;
...
esac
\end{lstlisting}
这看起来可能有点吓人，但是case结构使您能够匹配变量的内容

以一种相当复杂的方式对抗模式，然后允许执行不同的语句，这取决于

哪个图案是匹配的。它比另一种检查几个条件的方法简单得多，

也就是使用多个if、elif和else语句。

注意，每个模式行都以双分号(;;)结尾。可以在每个模式和下一个模式之间放置多个状态，因此需要一个双分号来标记语句的位置

结束，下一个模式开始。

匹配多个模式，然后执行多个相关语句的能力就是这样

构造一个处理用户输入的好方法。了解案例如何运作的最好方法是举个例子。

我们将通过三个Try it Out示例来开发它，每次都改进模式匹配。

如果在pat模式中使用通配符(如“*”)，请注意大小写结构。问题是，第一个匹配的模式将被采用，即使稍后

模式匹配更精确
案例一:用户输入

您可以编写一个新版本的输入测试脚本，并使用case结构，使它有点

更有选择性和宽容的意外输入:
\begin{lstlisting}[language=sh]
#!/bin/sh
echo “Is it morning? Please answer yes or no”
read timeofday
case “$timeofday” in
yes) echo “Good Morning”;;
no ) echo “Good Afternoon”;;
y ) echo “Good Morning”;;
n ) echo “Good Afternoon”;;
* ) echo “Sorry, answer not recognized”;;
esac
exit 0
\end{lstlisting}
它是如何工作的

当case语句执行时，它获取timeofday的内容并将其与每个timeofday进行比较

依次串。只要字符串与输入匹配，case命令就执行下面的代码

)和结束。

case命令对用于比较的字符串执行正常展开。你可以

因此，指定后跟*通配符的字符串的一部分。使用单个*将匹配所有可能的

字符串，所以总是把一个放在其他匹配字符串的后面，以确保case语句以

如果没有匹配其他字符串，则执行默认操作。这是可能的，因为case语句com依次对每个字符串进行匹配。它不寻找最佳匹配，只寻找第一个匹配。默认条件通常是不可能的条件，因此使用*可以帮助调试脚本。
尝试案例二:将模式放在一起

前面的case结构显然比多个if语句版本更优雅，但是由

把这些模式放在一起，你可以做出一个更简洁的版本:
\begin{lstlisting}[language=sh]
#!/bin/sh
echo “Is it morning? Please answer yes or no”
read timeofday
case “$timeofday” in
yes | y | Yes | YES ) echo “Good Morning”;;
n* | N* ) echo “Good Afternoon”;;
* ) echo “Sorry, answer not recognized”;;
esac
exit 0
\end{lstlisting}
它是如何工作的

该脚本在case的每个条目中使用多个字符串，以便case测试几个不同的字符串

每个可能的表述。这使得脚本更短，经过练习，更容易阅读。这段代码

还展示了如何使用*通配符，尽管这可能会匹配意想不到的模式。对于example，如果用户输入never，那么它将被n*匹配，并显示Good Afternoon。

这不是预期的行为。还要注意，*通配符表达式在引号中不起作用。

案例三:执行多条语句

最后，为了使脚本可重用，当默认模式为时，需要使用不同的退出值

因为输入不被理解而使用:
\begin{lstlisting}[language=sh]
#!/bin/sh
echo “Is it morning? Please answer yes or no”
read timeofday
case “$timeofday” in
yes | y | Yes | YES )
echo “Good Morning”
echo “Up bright and early this morning”
;;
[nN]*)
echo “Good Afternoon”
;;
*)
echo “Sorry, answer not recognized”
echo “Please answer yes or no”
exit 1
;;
esac
exit 0
\end{lstlisting}
它是如何工作的

为了展示一种不同的模式匹配方式，这段代码改变了no case的匹配方式。

您还将看到如何为case语句中的每个模式执行多个语句。你必须

请注意将最明确的匹配放在前面，而将最一般的匹配放在最后。这很重要

因为case执行它找到的第一个匹配，而不是最佳匹配。如果你把*)放在前面，它总是

匹配，不管输入什么。

注意;;Before esac是可选的。不像C编程，在那里省略休息是糟糕的程序编程实践，省略最后的;;如果最后一种情况是默认的，因为没有其他情况，没有问题吗

情况将予以考虑。

为了使case匹配更强大，你可以使用这样的代码:
\begin{lstlisting}[language=sh]
[yY] | [Yy][Ee][Ss] )
\end{lstlisting}
这限制了允许的字母，同时允许多种答案，并提供了更多的控制

*通配符。

列表

有时，您希望在串行中连接命令
\begin{lstlisting}[language=sh]
if [ -f this_file ]; then
if [ -f that_file ]; then
if [ -f the_other_file ]; then
echo “All files present, and correct”
fi
fi
fi
\end{lstlisting}
或者你可能想要一系列条件中至少有一个为真:
\begin{lstlisting}[language=sh]
if [ -f this_file ]; then
foo=”True”
elif [ -f that_file ]; then
foo=”True”
elif [ -f the_other_file ]; then
foo=”True”
else
foo=”False”
fi
if [ “$foo” = “True” ]; then
echo “One of the files exists”
fi
\end{lstlisting}
虽然这些可以使用多个if语句实现，但您可以看到结果是awkward。shell有一对特殊的结构来处理命令列表:AND列表和

或列表。它们经常一起使用，但我们将分别回顾它们的语法。

AND列表

AND列表结构使您能够执行一系列命令，仅执行下一个命令

如果以上命令都执行成功。语法是

statement1 \&\& statement2 \&\& statement3 \&\& \dots
从左边开始，执行每条语句;如果返回true，则执行右边的下一条语句。

这个过程一直持续到某个语句返回false，之后不再执行列表中的任何语句。的

\&\&测试上述命令的条件。
每条语句都是独立执行的，因此可以在一条语句中混合使用许多不同的命令

列表，如下面的脚本所示。如果所有命令(如)都执行成功，则AND列表作为一个整体成功，否则失败
试试它和列表

在下面的脚本中，触摸file\_one(检查它是否存在，如果不存在则创建它)和

然后删除file\_two。然后，AND列表测试每个文件是否存在，并回显一些
中间的文字
\begin{lstlisting}[language=sh]
#!/bin/sh
touch file_one
rm -f file_two
if [ -f file_one ] && echo “hello” && [ -f file_two ] && echo “ there”
then
echo “in if”
else
echo “in else”
fi
exit 0
\end{lstlisting}
试试这个脚本，你会得到以下结果:
hello
in else
它是如何工作的

touch和rm命令确保当前目录中的文件处于已知状态。\&\&列表

然后执行\verb|[-f file_one]|语句，它会成功，因为您刚刚确保了文件

存在。因为前面的语句成功，所以执行echo命令。这也成功了

(echo总是返回true)。然后执行第三个测试\verb|[-f file_two]|。失败是因为文件

不存在。因为最后一个命令失败了，所以没有执行最后的echo语句。\&\&的结果

List为false，因为列表中的一个命令失败，因此if语句执行其else条件。
手术室列表

OR列表结构使我们能够执行一系列命令，直到其中一个成功，然后不再执行exe-cute。语法如下:
\begin{lstlisting}[language=sh]
statement1 ||statement2 || statement3 || ....
\begin{lstlisting}[language=sh]
从左边开始，执行每条语句。如果返回false，则右边的下一条语句为

执行。这个过程一直持续到语句返回true，此时不再执行任何语句。
列表与\&\&列表非常相似，不同之处在于执行下一条语句的规则是
前一个语句必须失败。
\begin{lstlisting}[language=sh]
#!/bin/sh
rm -f file_one
if [ -f file_one ] || echo “hello” || echo “ there” 
then
echo “in if”
else
echo “in else”
fi
exit 0
\begin{lstlisting}[language=sh]
这将导致以下输出:
hello
in if
它是如何工作的

前两行只是为脚本的其余部分设置文件。第一个命令\verb|[-f file_one]|，

失败，因为文件不存在。然后执行echo语句。惊喜，惊喜——这又回来了

True，并且不再执行\|列表中的命令。if成功是因为其中一个命令在列表中(回显)为真。\\
这两个构造的结果都是要执行的最后一条语句的结果。

当存在多个条件时，这些列表类型结构的执行方式与C中的类似

测试。只执行最小数量的语句来确定结果。语句

不能影响不执行的结果。这通常被称为短路评估。

结合这两个构念是逻辑学家的天堂。试试以下方法:
\begin{lstlisting}[language=sh]
[ -f file_one ] && command for true || command for false
\end{lstlisting}
如果测试成功，将执行第一个命令，否则执行第二个命令。它总是

最好尝试使用这些不常见的列表，通常应该使用大括号强制排序

的评估。

语句块

如果您希望在只允许使用一条语句的地方使用多条语句，例如在AND或or中

列表中，可以通过将它们用大括号{}括起来形成语句块来实现。例如，在本章后面介绍的应用程序教育中，您将看到以下代码:
\begin{lstlisting}[language=sh]
get_confirm && {
grep -v “$cdcatnum” $tracks_file > $temp_file
cat $temp_file > $tracks_file
echo
add_record_tracks
}
\end{lstlisting}
功能

你可以在shell中定义函数;如果您编写任何大小的shell脚本，您都希望使用它们来

构建代码。

要定义一个shell函数，只需将其名称后面加上空括号，并将状态ments括在大括号中:
作为一种替代方法，您可以将一个大脚本拆分成许多小脚本，每个小脚本

它执行一个小任务。这有一些缺点:执行第二个脚本

从脚本中执行比执行函数要慢得多。更难

传回结果后，可能会有非常多的小脚本。你应该

考虑脚本中最小的独立部分，并将其用作

您对何时将一个大脚本分解为多个小脚本的度量
\begin{lstlisting}[language=sh]
function_name () {
statements
}
\end{lstlisting}
用一个简单的函数试试

让我们从一个非常简单的函数开始:
\begin{lstlisting}[language=sh]
#!/bin/sh
foo() {
echo “Function foo is executing”
}
echo “script starting”
foo
echo “script ended”
exit 0
\end{lstlisting}
运行该脚本将输出如下内容
script starting
Function foo is executing
script ending
它是如何工作的

这个脚本从顶部开始执行，所以没有什么不同，但是当它找到foo()\ {con结构体时，它知道正在定义一个名为foo的函数。它存储了foo指向一个函数的事实

并在匹配\}之后继续执行。当执行单行foo时，shell知道

执行前面定义的函数。当这个函数完成后，在该行继续执行

调用foo之后。

您必须始终在调用函数之前定义它，有点像Pascal风格的函数定义在调用之前定义，只是shell中没有前向声明。这不是问题，

因为所有脚本都是从顶部开始执行的，所以只需将所有函数放在任何函数的第一次调用之前

函数将总是导致所有函数在被调用之前被定义。

当调用函数时，脚本的位置参数\verb|$*$@ $#$1$2|等为

由函数的参数代替。这就是如何读取传递给函数的参数。

当函数结束时，它们将恢复为先前的值。
\begin{lstlisting}[language=sh]
foo () { echo JAY;}
...
result=”$(foo)“
\end{lstlisting}
注意，可以通过使用local关键字在shell函数中声明局部变量。变量是

然后只在函数的作用域中。否则，该函数可以访问其他shell变量

本质上是全局范围。如果局部变量与全局变量具有相同的名称，它会覆盖该变量，

但只在函数内。例如，您可以对前面的脚本进行以下更改以查看

这是在行动:
\begin{lstlisting}[language=sh]
#!/bin/sh
sample_text=”global variable”
foo() {
local sample_text=”local variable”
echo “Function foo is executing”
echo $sample_text
}
echo “script starting”
echo $sample_text
foo
echo “script ended” 
echo $sample_text
exit 0
\end{lstlisting}
如果没有指定返回值的返回命令，则函数返回对象的退出状态

最后执行的命令。

尝试返回一个值

下一个脚本\verb|my_name|展示了如何向函数传递参数以及函数如何返回
一个真或假的结果。使用要在问题中使用的名称的参数调用该脚本。

1. 在shell头文件之后，定义函数\verb|yes_or_no|:
\begin{lstlisting}[language=sh]
#!/bin/sh
yes_or_no() {
echo “Is your name $* ?”
while true
do
echo -n “Enter yes or no: “
read x
case “$x” in
y | yes ) return 0;;
n | no ) return 1;;
* ) echo “Answer yes or no”
esac
done
}
\end{lstlisting}
然后节目的主要部分开始了:
\begin{lstlisting}[language=sh]
echo “Original parameters are $*“
if yes_or_no “$1”
then
echo “Hi $1, nice name”
else
echo “Never mind”
fi
exit 0
\end{lstlisting}
该脚本的典型输出可能如下所示:
\begin{lstlisting}[language=sh]
\$ ./my_name Rick Neil
Original parameters are Rick Neil
Is your name Rick ?
\end{lstlisting}
它是如何工作的

在脚本执行时，定义了\verb|yes_or_no|函数，但尚未执行。在if语句中，
script执行函数\verb|yes_or_no|，在sub将带有第一个参数的\$1设置给原始脚本Rick之后，将该行的其余部分作为参数传递给函数。函数使用这些参数，

它们现在存储在位置参数\$1、\$2等中，并向调用者返回一个值。

根据返回值，if构造执行适当的语句。

如您所见，shell有一组丰富的控制结构和条件语句。你需要

学习一些shell内建的命令;然后，您就可以在没有编译器的情况下解决真正的程序编程问题了
命令

您可以在shell脚本中执行两种类型的命令。有一些“正常”的命令

您还可以从命令提示符执行(称为外部命令)，并且如前所述，有“内置”com命令(称为内部命令)。内置命令是在内部实现的

shell和不能作为外部程序调用。但是，也提供了大多数内部命令

作为独立程序——这个要求是POSIX规范的一部分。通常情况下，这并不重要

命令可以是内部命令，也可以是外部命令，只不过内部命令执行效率更高。

在这里，我们将只介绍在编写ming脚本时使用的内部和外部主要命令。作为一个Linux用户，您可能知道许多其他命令在该命令处是有效的

提示。始终记住，除了内置命令之外，您还可以在脚本中使用这些命令中的任何一个

这里介绍
\begin{lstlisting}[language=sh]
#!/bin/sh
rm -rf fred*
echo > fred1
echo > fred2
mkdir fred3
echo > fred4
for file in fred*
do
if [ -d “$file” ]; then
break;
fi
done
echo first directory starting fred was $file
rm -rf fred*
exit 0
\end{lstlisting}
命令

冒号命令为空命令。有时简化条件的逻辑是有用的，因为

true的别名。由于它是内置的，:运行速度比true快，尽管它的输出可读性也差得多。

你可能会看到它被用作while循环的条件;While:实现一个无限循环来代替

更常见的是真实的。

构造在变量的条件设置中也很有用。例如,
\begin{lstlisting}[language=sh]
: ${var:=value}
\end{lstlisting}
如果没有:，shell将尝试将\verb|$var|作为命令进行计算
在一些(主要是较旧的)shell脚本中，您可能会在一行的开头看到冒号

为了引入注释，但是现代脚本应该总是使用\#来开始注释

行，因为这样执行起来更有效率。
\begin{lstlisting}[language=sh]
#!/bin/sh
rm -f fred
if [ -f fred ]; then
:
else
echo file fred did not exist
fi
exit 0
\end{lstlisting}
与C中的同名语句类似，该命令执行for、while或until的封闭语句

循环在下一次迭代时继续，循环变量取列表中的下一个值:
\begin{lstlisting}[language=sh]
#!/bin/sh
rm -rf fred*
echo > fred1
echo > fred2
mkdir fred3
echo > fred4
for file in fred*
do
if [ -d “$file” ]; then
echo “skipping directory $file”
continue
fi
echo file is $file
done
rm -rf fred*
exit 0
\end{lstlisting}
Continue可以将恢复所在的封闭循环编号作为可选参数，以便您

可以部分跳出嵌套循环。这个参数很少使用，因为它经常使脚本变得非常复杂

更难理解。例如,
\begin{lstlisting}[language=sh]
for x in 1 2 3
do
echo before $x
continue 1
echo after $x
done
\end{lstlisting}
前面的输出将是
before 1
before 2
before 3
点(.)命令在当前shell中执行命令:
\begin{lstlisting}[language=sh]
. ./shell_script
\end{lstlisting}
通常，当脚本执行外部命令或脚本时，将创建一个新环境(子shell) ，在新环境中执行该命令，然后单独丢弃该环境

从返回给父shell的退出代码中获取。但是，外部源和。com命令(另外两个同义词)在调用脚本的同一shell中运行脚本中列出的命令。

因为在默认情况下，在执行shell脚本时创建一个新环境，因此脚本对环境环境变量所做的任何更改都将丢失。另一方面，点命令允许执行

脚本更改当前环境。当您使用脚本作为包装器进行设置时，这通常很有用

稍后执行其他命令的环境。比如，当你在做

同时在几个不同的项目中，您可能会发现您需要调用不同的命令

参数，可能是为了调用旧版本的编译器来维护旧程序。

在shell脚本中，点命令的工作方式有点像C或c++中的\#include指令。虽然事实并非如此

从字面上包含脚本，它在当前上下文中执行命令，因此可以使用它将合并变量和函数定义纳入脚本。
试试点命令

下面的示例在命令行中使用了点命令，但是您也可以使用它

在脚本中:

1. 假设您有两个文件，其中包含两个不同开发的环境设置

环境。要为旧的经典命令\verb|classic_set|设置环境，您可以

使用以下方法:

\begin{lstlisting}[language=sh]
#!/bin/sh
version=classic
PATH=/usr/local/old_bin:/usr/bin:/bin:.
PS1=”classic> “
\end{lstlisting}
2. 对于新命令，使用latest set:
\begin{lstlisting}[language=sh]

\end{lstlisting}
\begin{lstlisting}[language=sh]
#!/bin/sh
version=latest
PATH=/usr/local/new_bin:/usr/bin:/bin:.
PS1=” latest version> “
\end{lstlisting}
您可以通过使用这些脚本和点命令来设置环境，如下面的示例会话:
\$ . ./classic\_set
classic> echo \$version
classic
classic> . /latest\_set
latest version> echo \$version
latest
latest version> 
它是如何工作的

脚本使用dot命令执行，因此每个脚本都在当前shell中执行。这

使脚本能够更改当前shell中的环境设置，即使更改也不会改变

当脚本完成执行时。

回声

尽管X/Open鼓励在现代shell中使用printf命令，但我们还是遵循了这一点

常用的做法是使用echo命令输出后跟换行符的字符串。

一个常见的问题是如何抑制换行符。不幸的是，不同版本的UNIX

实施不同的解决方案。Linux中常用的方法是使用
第二个选项echo -e确保对反斜杠转义字符(如\c)的解释

为了抑制换行符，启用了用于输出TAB的\verb|\t|和用于输出回车的\verb|\n|。在

旧版本的bash通常默认设置此值，但较新的版本通常默认不解释反斜杠转义字符。有关您的发行版上的行为的详细信息，请参阅手册页面。
eval

eval命令允许您对参数求值。它是内置在外壳里的，通常不会

作为单独的命令存在。最好的例子是借用一个简短的例子

X/开放规范本身:
\begin{lstlisting}[language=sh]
foo=10
x=foo
y=’$’$x
echo $y
\end{lstlisting}
这给出了输出\$foo。然而,
\begin{lstlisting}[language=sh]
foo=10
x=foo
eval y=’$’$x
echo $y
\end{lstlisting}
给出输出10。因此，eval有点像额外的\$:它为您提供变量值的值。

eval命令非常有用，它使代码能够实时生成和运行。这确实很复杂

脚本调试，但它使您能够做一些很难甚至不可能做的事情。

执行

exec命令有两种不同的用途。它的典型用途是用不同的shell替换当前的shell

程序。例如
\begin{lstlisting}[language=sh]
exec wall “Thanks for all the fish”
\end{lstlisting}
在脚本中将当前shell替换为wall命令。脚本中没有行在执行之后

将被处理，因为正在执行该脚本的shell已不存在。
exec的第二个用途是修改当前文件描述符:
\begin{lstlisting}[language=sh]
exec 3< afile
\end{lstlisting}
这将导致打开文件描述符3以便从文件文件中读取。它很少被使用。

退出n

exit命令将导致脚本退出，退出码为n。如果在命令提示符下使用

任何交互式shell都会让你退出。如果允许脚本退出而不指定退出状态，

然后使用脚本中最后执行的命令的状态作为返回值。总是好的

练习提供退出代码。

在shell脚本编程中，退出代码0表示成功，而代码1到125(包括)是错误代码

可以被脚本使用。其余各值含义保留，如下表所示:
\begin{table}[p!]
\caption{0ne}
\centering
\begin{tabular}{|c|c|}
\hline
Exit Code & Description \\
\hline
126 & The file was not executable \\
\hline
127 & A command was not found \\
\hline
128 and above & A signal occurred \\
\hline
\end{tabular}
\end{table}
对于许多C或c++程序员来说，使用零作为成功似乎有点不寻常。最大的优势是

脚本使您能够使用125个用户定义的错误代码，而不需要全局错误

代码变量。

下面是一个简单的例子，如果当前目录中存在一个名为.profile的文件，则返回成功:

\begin{lstlisting}[language=sh]
#!/bin/sh
if [ -f .profile ]; then
exit 0
fi
exit 1
\end{lstlisting}
如果你喜欢惩罚，或者至少喜欢简洁的脚本，你可以使用前面所示的com AND AND AND or组合列表重写这个脚本，它们都在一行上:
\begin{lstlisting}[language=sh]
[ -f .profile ] && exit 0 || exit 1
\end{lstlisting}
出口

export命令使命名为其参数的变量在子shell中可用。默认情况下,

在shell中创建的变量在从该shell调用的其他(子)shell中不可用。出口

命令从其参数创建一个环境变量，该参数可以被从当前程序调用的其他脚本和程序程序看到。从技术上讲，导出的变量形成了从shell派生的任何子进程中的环境变量。的例子最好地说明了这一点

两个脚本，export1和export2。
\begin{lstlisting}[language=sh]
#!/bin/sh
echo “$foo”
echo “$bar”
\end{lstlisting}
2. 现在说说出口。在这个脚本的末尾，调用export2:
\begin{lstlisting}[language=sh]
#!/bin/sh
foo=”The first meta-syntactic variable”
export bar=”The second meta-syntactic variable”
export2
\end{lstlisting}
如果你运行这些，你会得到以下结果:
\$ ./export1
The second meta-syntactic variable
\$

它是如何工作的

export2脚本只是回显这两个变量的值。export1脚本设置了两个变量表，但只将bar标记为已导出，因此当它随后调用export1时，foo的值为已导出

丢失了，但是bar的值已经导出到第二个脚本。出现空行是因为

\verb|$foo|求值为零，并且回显null变量给出一个换行符。

从shell导出变量后，它将被导出到从该shell调用的任何脚本中

到它们依次调用的任何shell，以此类推。如果脚本export2调用另一个脚本，它也会有

可用的bar值。
命令set -a或set - alleexport将导出所有变量
expr

expr命令将其参数计算为表达式。它最常用于以下形式的简单算术算术:
\begin{lstlisting}[language=sh]
x=`expr $x + 1`
\end{lstlisting}
' '(反打号)字符使x取执行命令expr \$x + 1的结果。你可以

也可以使用语法\$()而不是反引号来写，像这样:
\begin{lstlisting}[language=sh]
x=$(expr $x + 1)
\end{lstlisting}
expr命令功能强大，可以执行许多表达式求值。主要的是

如下表所示
\begin{table}[p!]
\caption{two}
\centering
\begin{tabular}{|c|c|}
\hline
 \textbackslash `` & Double quote \\
\hline
 \textbackslash \textbackslash & Backslash character \\
\hline
\textbackslash a & Alert (ring the bell or beep) \\
\hline
\textbackslash b & Backspace character \\
\hline
\textbackslash c &Suppress further output\\
\hline
\textbackslash f &Form feed character\\
\hline
\textbackslash n & Newline character\\
\hline
\textbackslash r & Carriage return\\
\hline
\textbackslash t & Tab character \\
\hline
\textbackslash v &Vertical tab character \\
\hline
\textbackslash ooo & The single character with octal value ooo \\
\hline
\textbackslash xHH & The single character with the hexadecimal value HH\\
\hline
\end{tabular}
\end{table}
转换说明符非常复杂，因此我们在这里只列出常见的用法。更多详情可浏览

可以在bash在线手册中找到，也可以在在线手册第1节的printf页面中找到

printf)。(如果您在手册的第1节中找不到它，请尝试第3节作为替代)。转换

说明符由一个\%字符和一个转换字符组成。主要的转换是

如下表所示:

\begin{table}[p!]
\caption{three}
\centering
\begin{tabular}{|c|c|}
\hline
 D & Output a decimal number \\
\hline
C & Output a character. \\
\hline
S & Output a string. \\
\hline
\% & Output the \% character. \\
\hline
\end{tabular}
\end{table}
然后使用格式字符串解释剩下的参数并输出结果，如

下面的例子

\$ printf “\%s \verb|\n|” hello
hello
\$ printf “\%s \%d \verb|\t| \%s” “Hi There” 15 people
Hi There 15 people



\bibliographystyle{plain}
\bibliography{crazyfish.bib}

\end{document}